<?php

/**
 * NOTICE OF LICENSE
 *
 * THIS SOURCE FILE IS PART OF EVEBIT'S PRIVATE PROJECT.
 * 
 * DO NOT USE THIS FILE IN OTHER PLACE.
 *
 * @category  Evebit
 * @package   App_Model
 * @author    Cheng Wei <berlios.o@gmail.com>
 * @version   $$Id: Abstract.php 130 2011-03-18 03:10:02Z cheng.wei $$
 * @copyright Copyright (c) 2008 Evebit Inc. China (http://www.evebit.com)
 */

/**
 * Abstract model using mysql
 * 
 * @package App_Model
 * @author  Cheng Wei <berlios.o@gmail.com>
 * @version $$Id: Abstract.php 130 2011-03-18 03:10:02Z cheng.wei $$
 */
abstract class Evebit_Model_Mysql_Abstract extends Evebit_Model_Resource_Abstract
{
    const CHECKSUM_KEY_NAME = 'Checksum';

    /**
     * Cached resources singleton
     *
     * @var Evebit_Model_Resource
     */
    protected $_resources;

    /**
     * Prefix for resources that will be used in this resource model
     *
     * @var string
     */
    protected $_resourcePrefix;

    /**
     * Connections cache for this resource model
     *
     * @var array
     */
    protected $_connections = array();

    /**
     * Resource model name that contains entities (names of tables)
     *
     * @var string
     */
    protected $_resourceModel;

    /**
     * Tables used in this resource model
     *
     * @var array
     */
    protected $_tables = array();

    /**
     * Main table name
     *
     * @var string
     */
    protected $_mainTable;

    /**
     * Main table primary key field name
     *
     * @var string
     */
    protected $_idFieldName;

    /**
     * Primery key auto increment flag
     *
     * @var bool
     */
    protected $_isPkAutoIncrement = true;

    protected $_mainTableFields;

    /**
     * Main table unique keys field names
     *
     * could array(
     *   array('field' => 'db_field_name1', 'title' => 'Field 1 should be unique')
     *   array('field' => 'db_field_name2', 'title' => 'Field 2 should be unique')
     *   array(
     *      'field' => array('db_field_name3', 'db_field_name3'),
     *      'title' => 'Field 3 and Field 4 combination should be unique'
     *   )
     * )
     *
     * or string 'my_field_name' - will be autoconverted to
     *      array( array( 'field' => 'my_field_name', 'title' => 'my_field_name' ) )
     *
     * @var array
     */
    protected $_uniqueFields = null;

	protected $_datetimeFields = array();
	
    /**
     * Standard resource model initialization
     *
     * @param string $mainTable
     * @param string $idFieldName
     * @return Evebit_Model_Mysql4_Abstract
     */
    protected function _init($mainTable, $idFieldName = 'id')
    {
        $this->_setMainTable($mainTable, $idFieldName);
    }
    /**
     * Set main entity table name and primary key field name
     *
     * If field name is ommited {table_name}_id will be used
     *
     * @param string $mainTable
     * @param string|null $idFieldName
     * @return Evebit_Model_Mysql4_Abstract
     */
    protected function _setMainTable($mainTable, $idFieldName=null)
    {
		$this->_mainTable = $mainTable;
		if (is_null($idFieldName)) {
        	$idFieldName = 'id';
		}
		$this->_idFieldName = $idFieldName;

        return $this;
    }

    /**
     * Get primary key field name
     *
     * @return string
     */
    public function getIdFieldName()
    {
        if (empty($this->_idFieldName)) {
			$this->_idFieldName = 'id';
        }
        return $this->_idFieldName;
    }

    /**
     * Get main table name
     *
     * @return string
     */
    public function getMainTable()
    {
        if (empty($this->_mainTable)) {
            throw new Exception('Empty main table name');
        }
        return $this->getTable($this->_mainTable);
    }

    /**
     * Get table name for the entity
     *
     * @param string $entityName
     */
    public function getTable($entityName)
    {
        if (isset($this->_tables[$entityName])) {
            return $this->_tables[$entityName];
        }
        if (!empty($this->_resourceModel)) {
            $this->_tables[$entityName] = $this->_resourceModel.'_'.$entityName;
        } else {
            $this->_tables[$entityName] = $entityName;
        }
        return $this->_tables[$entityName];
    }

    /**
     * Get connection by name or type
     *
     * @param   string $connectionName
     * @return  Zend_Db_Adapter_Abstract
     */
    protected function _getConnection($connectionName)
    {
    	return Zend_Db_Table::getDefaultAdapter();
    }

    /**
     * Retrieve connection for read data
     *
     * @return  Zend_Db_Adapter_Abstract
     */
    protected function _getReadAdapter()
    {
        return $this->_getConnection('read');
    }

    /**
     * Retrieve connection for write data
     *
     * @return  Zend_Db_Adapter_Abstract
     */
    protected function _getWriteAdapter()
    {
        return $this->_getConnection('write');
    }

    /**
     * Temporary resolving collection compatibility
     *
     * @return Zend_Db_Adapter_Abstract
     */
    public function getReadConnection()
    {
        return $this->_getReadAdapter();
    }

    /**
     * Load an object
     *
     * @param   Evebit_Model_Abstract $object
     * @param   mixed $value
     * @param   string $field field to load by (defaults to model id)
     * @return  Evebit_Model_Mysql4_Abstract
     */
    public function load(Evebit_Model_Abstract $object, $value, $field=null)
    {
        if (is_null($field)) {
            $field = $this->getIdFieldName();
        }

        $read = $this->_getReadAdapter();
        if ($read && !is_null($value)) {
            $select = $this->_getLoadSelect($field, $value, $object);
            $data = $read->fetchRow($select);

            if ($data) {
                $object->setData($data);
            }
        }

        $this->_afterLoad($object);

        return $this;
    }

    /**
     * Retrieve select object for load object data
     *
     * @param   string $field
     * @param   mixed $value
     * @return  Zend_Db_Select
     */
    protected function _getLoadSelect($field, $value, $object)
    {
           $select = $this->_getReadAdapter()->select()
            ->from($this->getMainTable())
            ->where($this->getMainTable().'.'.$field.'=?', $value);
        return $select;
    }

    /**
     * Save object object data
     *
     * @param   Evebit_Model_Abstract $object
     * @return  Evebit_Model_Mysql4_Abstract
     */
    public function save(Evebit_Model_Abstract $object)
    {
        if ($object->isDeleted()) {
            return $this->delete($object);
        }

        $this->_beforeSave($object);
        $this->_checkUnique($object);

        if (!is_null($object->getId())) {
            $condition = $this->_getWriteAdapter()->quoteInto($this->getIdFieldName().'=?', $object->getId());
            /**
             * Not auto increment primary key support
             */
            if ($this->_isPkAutoIncrement) {
                $this->_getWriteAdapter()->update($this->getMainTable(), $this->_prepareDataForSave($object), $condition);
            } else {
                $select = $this->_getWriteAdapter()->select($this->getMainTable(), array($this->getIdFieldName()))
                    ->where($condition);
                if ($this->_getWriteAdapter()->fetchOne($select) !== false) {
                    $this->_getWriteAdapter()->update($this->getMainTable(), $this->_prepareDataForSave($object), $condition);
                } else {
                    $this->_getWriteAdapter()->insert($this->getMainTable(), $this->_prepareDataForSave($object));
                }
            }
        } else {
            $this->_getWriteAdapter()->insert($this->getMainTable(), $this->_prepareDataForSave($object));
            $object->setId($this->_getWriteAdapter()->lastInsertId($this->getMainTable()));
        }

        $this->_afterSave($object);

        return $this;
    }

    /**
     * Delete the object
     *
     * @param Varien_Object $object
     * @return Evebit_Model_Mysql4_Abstract
     */
    public function delete(Evebit_Model_Abstract $object)
    {
        $this->_beforeDelete($object);
        $this->_getWriteAdapter()->delete(
            $this->getMainTable(),
            $this->_getWriteAdapter()->quoteInto($this->getIdFieldName().'=?', $object->getId())
        );
        $this->_afterDelete($object);
        return $this;
    }

    /**
     * Add unique field restriction
     *
     * @param   array|string $field
     * @return  Evebit_Model_Mysql4_Abstract
     */
    public function addUniqueField($field)
    {
        $this->_initUniqueFields();
        if(is_array($this->_uniqueFields) ) {
            $this->_uniqueFields[] = $field;
        }
        return $this;
    }

    /**
     * Reset unique fields restrictions
     *
     * @return Evebit_Model_Mysql4_Abstract
     */
    public function resetUniqueField()
    {
         $this->_uniqueFields = array();
         return $this;
    }

    /**
     * Initialize unique fields
     *
     * @return Evebit_Model_Mysql4_Abstract
     */
    protected function _initUniqueFields()
    {
        $this->_uniqueFields = array();
        return $this;
    }

    /**
     * Get configuration of all unique fields
     *
     * @return array
     */
    public function getUniqueFields()
    {
        if (is_null($this->_uniqueFields)) {
            $this->_initUniqueFields();
        }
        return $this->_uniqueFields;
    }

    /**
     * Prepare data for save
     *
     * @param   Evebit_Model_Abstract $object
     * @return  array
     */
    protected function _prepareDataForSave(Evebit_Model_Abstract $object)
    {
        $data = array();
        $fields = $this->_getWriteAdapter()->describeTable($this->getMainTable());
        foreach (array_keys($fields) as $field) {
            if ($object->hasData($field)) {
                $fieldValue = $object->getData($field);
                if ($fieldValue instanceof Zend_Db_Expr) {
                    $data[$field] = $fieldValue;
                }
                else {
                    if (null !== $fieldValue) {
                        $data[$field] = $this->_prepareValueForSave($fieldValue, $fields[$field]['DATA_TYPE']);
                    }
                    elseif (!empty($fields[$field]['NULLABLE'])) {
                        $data[$field] = null;
                    }
                }
            }
        }
        
		foreach ($this->_datetimeFields as $field) {
			if (!$object->hasData($field)) {
				unset($data[$field]);
				continue;
			}
			$val = $object->getData($field);
			if ($val && !$val instanceof Zend_Date) {
				$val = new Zend_Date($val);
			} 
			if (!$val) {
				$data[$field] = "0000-00-00 00:00:00";
				continue;
			}			
			$data[$field] = $val->toString(Zend_Date::ISO_8601);			
		}
		
        return $data;
    }

    /**
     * Prepare value for save
     *
     * @param mixed $value
     * @param string $type
     * @return mixed
     */
    protected function _prepareValueForSave($value, $type)
    {
        return $value;
    }

    /**
     * Check for unique values existence
     *
     * @param   Varien_Object $object
     * @return  Evebit_Model_Mysql4_Abstract
     * @throws  Mage_Core_Exception
     */
    protected function _checkUnique(Evebit_Model_Abstract $object)
    {
        $existent = array();
        $fields = $this->getUniqueFields();
        if (!empty($fields)) {
            if (!is_array($fields)) {
                $this->_uniqueFields = array(
                    array(
                        'field' => $fields,
                        'title' => $fields
                ));
            }

            $data = new Varien_Object($this->_prepareDataForSave($object));
            $select = $this->_getWriteAdapter()->select()
                ->from($this->getMainTable());

            foreach ($fields as $unique) {
                $select->reset(Zend_Db_Select::WHERE);

                if (is_array($unique['field'])) {
                    foreach ($unique['field'] as $field) {
                        $select->where($field.'=?', $data->getData($field));
                    }
                }
                else {
                    $select->where( $unique['field'] . ' = ?', $data->getData($unique['field']) );
                }

                if ($object->getId()) {
                    $select->where($this->getIdFieldName().' != ?', $object->getId());
                }

                if ( $test = $this->_getWriteAdapter()->fetchRow($select) ) {
                    $existent[] = $unique['title'];
                }
            }
        }

        if (!empty($existent)) {
            if (count($existent) == 1 ) {
                $error = sprintf('%s already exist', $existent[0]);
            }
            else {
                $error = sprintf('%s already exists', implode(', ', $existent));
            }
            throw new Exception($error);
        }
        return $this;
    }

    public function afterLoad(Evebit_Model_Abstract $object)
    {
        $this->_afterLoad($object);
    }

    /**
     * Perform actions after object load
     *
     * @param Varien_Object $object
     */
    protected function _afterLoad(Evebit_Model_Abstract $object)
    {	    	
    	foreach ($this->_datetimeFields as $field) {
			if ($object->hasData($field)) {
				$data = $object->getData($field);
				if ($data && !$data instanceof Zend_Date) {					
					$data = new Zend_Date($data,Zend_Date::ISO_8601);
					$data->setTimezone(date_default_timezone_get());					
				} 

				if (!$data){
					$object->unsetData($field);
					continue;
				}
				
				$object->setData($field, $data);
			}
		}
        return $this;
    }

    /**
     * Perform actions before object save
     *
     * @param Varien_Object $object
     */
    protected function _beforeSave(Evebit_Model_Abstract $object)
    {    	
    	foreach ($this->_datetimeFields as $field) {
			if ($object->hasData($field)) {
				$data = $object->getData($field);
				if ($data && !$data instanceof Zend_Date) {
					$data = new Zend_Date($data);
				} else {
					$object->unsetData($field);
				}
				$object->setData($field, $data);
			}
		}
        return $this;
    }

    /**
     * Perform actions after object save
     *
     * @param Varien_Object $object
     */
    protected function _afterSave(Evebit_Model_Abstract $object)
    {
        return $this;
    }

    /**
     * Perform actions before object delete
     *
     * @param Varien_Object $object
     */
    protected function _beforeDelete(Evebit_Model_Abstract $object)
    {
        return $this;
    }

    /**
     * Perform actions after object delete
     *
     * @param Varien_Object $object
     */
    protected function _afterDelete(Evebit_Model_Abstract $object)
    {
        return $this;
    }

    /**
     * Retrieve table checksum
     *
     * @param   string $table
     * @return  int
     */
    public function getChecksum($table)
    {
        if (!$this->_getConnection('read')) {
            return false;
        }

        if (is_array($table)) {
            $table = implode(',', $table);
        }

        $data = $this->_getConnection('read')->fetchAll('checksum table '.$table);
        $checksum = 0;
        foreach ($data as $row) {
            $checksum+= $row[self::CHECKSUM_KEY_NAME];
        }
        return $checksum;
    }
    

	/**
	 * Check unique valid is valid or not
	 *  
	 * @param string $field
	 * @param string $value
	 * 
	 * @param int $id if $id = 0, check for new object
	 *                if $id > 0, check for modify object
	 * 
	 * @return bool
	 */
	public function isUniqueValid($field, $value, $id) {
		$table = $this->getMainTable();
		$db = $this->getReadConnection();
		
		$select = $db->select()->from(array('main_table'=>$table), 'id');
		$select->where($db->quoteIdentifier($field)." = ?", $value);
		if ($id) {
			$select->where($this->getIdFieldName()." != ?", $id);
		}
		$select->limit(1);		
		return $db->fetchOne($select) ? true : false;
	}
	
	/**
	 * Get the data of the special table
	 * @param String $table The table name
	 * @param Object | Array $object The object or Array
	 * @param Bool $isId Default false. The return data without id
	 * @return Array
	 */
    public function getTableData($table, $object, $isId = false) {
		$db = $this->getReadConnection ();
		$fields = $db->describeTable ( $table );
		if(is_object($object)){
			$datas = $object->getData ();
		} elseif (is_array($object)) {
			$datas = $object;
		} else {
			return null;
		}
		
		foreach ( $fields as $key => $value ) {
			foreach ( $datas as $key2 => $value2 ) {
				if ($key == $key2) {
					$data [$key] = $value2;
					break 1;
				}
			}
		}
		if (! $isId && isset ( $data ['id'] )) {
			array_shift ( $data );
		}
		return $data;
	}
}
